package com.ms.code.generate;

import com.ms.base.comm.util.DateUtil;
import com.ms.base.comm.util.FrameUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.nio.charset.Charset;
import java.sql.*;
import java.util.Calendar;

/**
 * <b>description</b>： <br>
 * <b>time</b>：2018-08-06 18:03 <br>
 * <b>author</b>： ready likun_557@163.com
 */
@Slf4j
public class CodeGenerateUtil {
    public static void main(String[] args) throws Exception {
        String tableName = "demo";
        String jdbcUrl = "jdbc:mysql://localhost:3306/ms_master?characterEncoding=UTF-8";
        String jdbcDriver = "com.mysql.jdbc.Driver";
        String jdbcUserName = "root";
        String jdbcPassword = "root";
        String codeDirPath = "C:\\Users\\pc\\IdeaProjects1\\p-parent"; //p-parent目录
        boolean overwriteFile = true;//是否覆盖文件
        String prjName = "core";//项目名称
        String moduleName = "admin";//模块名称
        String remark = "测试";//备注
        String author = "ready likun_557@163.com";
        String dateTime = DateUtil.format(Calendar.getInstance().getTime(), DateUtil.PATTERN_yyyy_MM_dd_HH_mm_ss);

        log.info("开始生成代码.....");
        CodeData codeData = buildCodeData(CodeData.builder().tableConfig(
                TableConfig.builder().
                        tableName(tableName).
                        jdbcUrl(jdbcUrl).
                        jdbcDriver(jdbcDriver).
                        jdbcUserName(jdbcUserName).
                        jdbcPassword(jdbcPassword).
                        remark(remark).
                        build()).
                codeDirPath(codeDirPath).
                overwriteFile(overwriteFile).
                prjName(prjName).
                moduleName(moduleName).
                remark(remark).
                author(author).
                dateTime(dateTime).
                build());
        log.info("代码生成完毕.....");
    }

    /**
     * 构建CodeData
     *
     * @param codeData
     * @return
     */
    public static CodeData buildCodeData(CodeData codeData) throws Exception {
        codeData.setTableModel(buildTable(codeData.getTableConfig()));
        codeData.setModelData(buildModelData(codeData));
        codeData.setServiceData(buildServiceData(codeData));
        codeData.setServiceImplData(buildServiceImplData(codeData));
        codeData.setDaoData(buildDaoData(codeData));
        codeData.setDaoImplData(buildDaoImplData(codeData));
        codeData.setMapperData(buildMapperData(codeData));
        codeData.setSqlMapData(SqlMapData.builder().build());

        codeData.getModelData().setCodeFile(buildModelCodeFile(codeData));
        codeData.getServiceData().setCodeFile(buildServiceCodeFile(codeData));
        codeData.getServiceImplData().setCodeFile(buildServiceImplCodeFile(codeData));
        codeData.getDaoData().setCodeFile(buildDaoCodeFile(codeData));
        codeData.getDaoImplData().setCodeFile(buildDaoImplCodeFile(codeData));
        codeData.getMapperData().setCodeFile(buildMapperCodeFile(codeData));
        codeData.getSqlMapData().setCodeFile(buildSqlMapCodeFile(codeData));

        //保存
        saveFile(codeData);

        return codeData;
    }

    /**
     * @param codeData
     * @throws Exception
     */
    private static void saveFile(CodeData codeData) throws Exception {
        boolean overwriteFile = codeData.isOverwriteFile();
        writeFile(codeData.getModelData().getCodeFile(), overwriteFile);
        writeFile(codeData.getServiceData().getCodeFile(), overwriteFile);
        writeFile(codeData.getServiceImplData().getCodeFile(), overwriteFile);
        writeFile(codeData.getDaoData().getCodeFile(), overwriteFile);
        writeFile(codeData.getDaoImplData().getCodeFile(), overwriteFile);
        writeFile(codeData.getMapperData().getCodeFile(), overwriteFile);
        writeFile(codeData.getSqlMapData().getCodeFile(), overwriteFile);
    }

    private static void writeFile(CodeFile codeFile, boolean overwrite) throws Exception {
        File file = new File(codeFile.getFileDir(), codeFile.getFileName());
        if (!file.exists() || overwrite) {
            if (log.isDebugEnabled()) {
                log.debug("生成文件:{}", file.getAbsolutePath());
            }
            FileUtils.writeStringToFile(file, codeFile.getContent(), Charset.defaultCharset());
        }
    }

    /**
     * 构建model代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildModelCodeFile(CodeData codeData) throws Exception {
        String ftlName = "model";
        String codeDirPath = codeData.getCodeDirPath();
        ModelData modelData = codeData.getModelData();
        ClassData classData = modelData.getClassData();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s/p-%s-comm/src/main/java", prjName, prjName);
        String child = classData.getName().replaceAll("\\.", "/");
        String suffix = "java";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }

    /**
     * 构建service接口代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildServiceCodeFile(CodeData codeData) throws Exception {
        String ftlName = "service";
        String codeDirPath = codeData.getCodeDirPath();
        ServiceData serviceData = codeData.getServiceData();
        ClassData classData = serviceData.getClassData();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s\\p-%s-service\\src\\main\\java", prjName, prjName);
        String child = classData.getName().replaceAll("\\.", "/");
        String suffix = "java";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }

    /**
     * 构建service实现类代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildServiceImplCodeFile(CodeData codeData) throws Exception {
        String ftlName = "serviceImpl";
        String codeDirPath = codeData.getCodeDirPath();
        ServiceImplData serviceImplData = codeData.getServiceImplData();
        ClassData classData = serviceImplData.getClassData();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s\\p-%s-service\\src\\main\\java", prjName, prjName);
        String child = classData.getName().replaceAll("\\.", "/");
        String suffix = "java";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }

    /**
     * 构建dao接口类代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildDaoCodeFile(CodeData codeData) throws Exception {
        String ftlName = "dao";
        String codeDirPath = codeData.getCodeDirPath();
        DaoData daoData = codeData.getDaoData();
        ClassData classData = daoData.getClassData();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s\\p-%s-service\\src\\main\\java", prjName, prjName);
        String child = classData.getName().replaceAll("\\.", "/");
        String suffix = "java";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }

    /**
     * 构建dao实现类类代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildDaoImplCodeFile(CodeData codeData) throws Exception {
        String ftlName = "daoImpl";
        String codeDirPath = codeData.getCodeDirPath();
        DaoImplData daoImplData = codeData.getDaoImplData();
        ClassData classData = daoImplData.getClassData();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s\\p-%s-service\\src\\main\\java", prjName, prjName);
        String child = classData.getName().replaceAll("\\.", "/");
        String suffix = "java";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }

    /**
     * 构建mapper类类代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildMapperCodeFile(CodeData codeData) throws Exception {
        String ftlName = "mapper";
        String codeDirPath = codeData.getCodeDirPath();
        MapperData mapperData = codeData.getMapperData();
        ClassData classData = mapperData.getClassData();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s\\p-%s-service\\src\\main\\java", prjName, prjName);
        String child = classData.getName().replaceAll("\\.", "/");
        String suffix = "java";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }

    /**
     * 构建sqlmap代码文件对象
     *
     * @param codeData
     * @return
     * @throws Exception
     */
    public static CodeFile buildSqlMapCodeFile(CodeData codeData) throws Exception {
        String ftlName = "sqlmap";
        String codeDirPath = codeData.getCodeDirPath();
        String content = code(ftlName, codeData);
        String prjName = codeData.getPrjName();
        String prePath = String.format("p-%s\\p-%s-service\\src\\main\\resources\\mybatis\\mapper", prjName, prjName);
        String child = codeData.getModelData().getClassData().objName.substring(0, codeData.getModelData().getClassData().objName.length() - "Model".length());
        String suffix = "xml";
        File file = new File(String.format("%s/%s/%s.%s", codeDirPath, prePath, child, suffix));
        return CodeFile.builder().content(content).fileDir(file.getParent()).fileName(file.getName()).build();
    }


    /**
     * 获取代码
     *
     * @param ftlName  ftl名称
     * @param codeData
     * @return
     * @throws Exception
     */
    public static String code(String ftlName, CodeData codeData) throws Exception {
        return FreemarkerUtil.getFtlToString(ftlName, codeData);
    }

    /**
     * 构建ModelData
     *
     * @param codeData
     * @return
     */
    private static ModelData buildModelData(CodeData codeData) {
        TableModel tableModel = codeData.getTableModel();
        String tableName = tableModel.getName();
        String tname = tableName;
        String split = "_";
        String simpleName = String.format("%sModel", nameCamel(tname, split, true));
        String pkgName = String.format("com.ms.%s.comm.%s.model", codeData.getPrjName(), codeData.getModuleName());
        String name = String.format("%s.%s", pkgName, simpleName);
        String objName = String.format("%sModel", nameCamel(tname, split, false));
        return ModelData.builder().classData(ClassData.builder().simpleName(simpleName).pkgName(pkgName).name(name).objName(objName).build()).build();
    }

    /**
     * 构建ServiceData
     *
     * @param codeData
     * @return
     */
    private static ServiceData buildServiceData(CodeData codeData) {
        TableModel tableModel = codeData.getTableModel();
        String tableName = tableModel.getName();
        String tname = tableName;
        String split = "_";
        String simpleName = String.format("I%sService", nameCamel(tname, split, true));
        String pkgName = String.format("com.ms.%s.service.%s.service", codeData.getPrjName(), codeData.getModuleName());
        String name = String.format("%s.%s", pkgName, simpleName);
        String objName = String.format("%sService", nameCamel(tname, split, false));
        return ServiceData.builder().classData(ClassData.builder().simpleName(simpleName).pkgName(pkgName).name(name).objName(objName).build()).build();
    }

    /**
     * 构建ServiceImplData
     *
     * @param codeData
     * @return
     */
    private static ServiceImplData buildServiceImplData(CodeData codeData) {
        TableModel tableModel = codeData.getTableModel();
        String tableName = tableModel.getName();
        String tname = tableName;
        String split = "_";
        String simpleName = String.format("%sServiceImpl", nameCamel(tname, split, true));
        String pkgName = String.format("com.ms.%s.service.%s.service.impl", codeData.getPrjName(), codeData.getModuleName());
        String name = String.format("%s.%s", pkgName, simpleName);
        return ServiceImplData.builder().classData(ClassData.builder().simpleName(simpleName).pkgName(pkgName).name(name).build()).build();
    }

    /**
     * 构建DaoData
     *
     * @param codeData
     * @return
     */
    private static DaoData buildDaoData(CodeData codeData) {
        TableModel tableModel = codeData.getTableModel();
        String tableName = tableModel.getName();
        String tname = tableName;
        String split = "_";
        String simpleName = String.format("I%sDao", nameCamel(tname, split, true));
        String pkgName = String.format("com.ms.%s.service.%s.dao", codeData.getPrjName(), codeData.getModuleName());
        String name = String.format("%s.%s", pkgName, simpleName);
        String objName = String.format("%sDao", nameCamel(tname, split, false));
        return DaoData.builder().classData(ClassData.builder().simpleName(simpleName).pkgName(pkgName).name(name).objName(objName).build()).build();
    }

    /**
     * 构建DaoImplData
     *
     * @param codeData
     * @return
     */
    private static DaoImplData buildDaoImplData(CodeData codeData) {
        TableModel tableModel = codeData.getTableModel();
        String tableName = tableModel.getName();
        String tname = tableName;
        String split = "_";
        String simpleName = String.format("%sDaoImpl", nameCamel(tname, split, true));
        String pkgName = String.format("com.ms.%s.service.%s.dao.impl", codeData.getPrjName(), codeData.getModuleName());
        String name = String.format("%s.%s", pkgName, simpleName);
        return DaoImplData.builder().classData(ClassData.builder().simpleName(simpleName).pkgName(pkgName).name(name).build()).build();
    }

    /**
     * 构建MapperData
     *
     * @param codeData
     * @return
     */
    private static MapperData buildMapperData(CodeData codeData) {
        TableModel tableModel = codeData.getTableModel();
        String tableName = tableModel.getName();
        String tname = tableName;
        String split = "_";
        String simpleName = String.format("%sMapper", nameCamel(tname, split, true));
        String pkgName = String.format("com.ms.%s.service.%s.mapper", codeData.getPrjName(), codeData.getModuleName());
        String name = String.format("%s.%s", pkgName, simpleName);
        String objName = String.format("%sMapper", nameCamel(tname, split, false));
        return MapperData.builder().classData(ClassData.builder().simpleName(simpleName).pkgName(pkgName).name(name).objName(objName).build()).build();
    }

    /**
     * 将name转换为骆驼命名法对象
     *
     * @param name
     * @param split
     * @param firstIsUpper
     * @return
     */
    public static String nameCamel(String name, String split, boolean firstIsUpper) {
        String[] strs = name.toLowerCase().split(split);
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < strs.length; i++) {
            String s = strs[i];
            str.append((i == 0 ? s : (s.substring(0, 1).toUpperCase() + s.substring(1))));
        }
        return firstIsUpper ? str.substring(0, 1).toUpperCase() + str.substring(1) : str.toString();
    }

    public static String objName(String typeName) {
        return typeName.substring(0, 1).toLowerCase() + typeName.substring(1);
    }


    public static TableModel buildTable(TableConfig tableConfig) throws Exception {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        TableModel tableModel = TableModel.builder().
                name(tableConfig.getTableName()).
                remark(tableConfig.getRemark()).
                fieldModelList(FrameUtil.newArrayList()).build();
        try {
            conn = getConnection(tableConfig);
            DatabaseMetaData dbmd = conn.getMetaData();
            rs = dbmd.getColumns(null, "%", tableConfig.getTableName(), "%");
            while (rs.next()) {
                String name = rs.getString("COLUMN_NAME");
                String remark = rs.getString("REMARKS");
                boolean primary = rs.getString("IS_AUTOINCREMENT").toUpperCase().equals("YES");
                int type = rs.getInt("DATA_TYPE");
                FieldModel fieldModel = FieldModel.builder().
                        name(name.toLowerCase()).
                        remark(remark).
                        primary(primary).
                        type(type).
                        javaType(JdbcTypeJavaTypeMap.jdbcTypeJavaTypeMap.get(type)).
                        build();
                fieldModel.setJavaTypeName(fieldModel.getJavaType().getTypeName());
                tableModel.getFieldModelList().add(fieldModel);
                if (fieldModel.isPrimary()) {
                    tableModel.setPrimaryFieldModel(fieldModel);
                }
            }
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (ps != null) {
                    ps.close();
                }
                if (conn != null && !conn.isClosed()) {
                    conn.close();
                }
            } catch (SQLException e) {
                throw e;
            }
        }
        return tableModel;
    }

    private static Connection getConnection(TableConfig tableConfig) throws ClassNotFoundException, SQLException {
        Class.forName(tableConfig.getJdbcDriver());
        return DriverManager.getConnection(tableConfig.getJdbcUrl(), tableConfig.getJdbcUserName(), tableConfig.getJdbcPassword());
    }
}
